package us.icitap.timsportal.ws.util;

import java.security.Security;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.AttributeModificationException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InvalidAttributeValueException;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

import us.icitap.timsportal.business.DomainUser;

public class ADConnection {
//	private static final String LDAP_PRIMARY = "LDAP://192.168.150.10:389/DC=ASP,DC=gov,DC=al";
//	private static final String LDAP_SECONDARY = "LDAP://192.168.150.11:389/DC=ASP,DC=gov,DC=al";
	
	String domain = "@asp.gov.al";
	String dc = "DC=ASP,DC=gov,DC=al";
	String ipPrimary = "192.168.150.10", ipSecondary = "192.168.150.11";
		
	public ADConnection(){
	
		Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
		System.setProperty("javax.net.ssl.trustStore", "/us/icitap/timsportal/ws/util/jssecacerts.jks");
		System.setProperty("javax.net.debug", "all");
	}
	
	private LdapContext getConnection(String username, String password, boolean ssl){
		System.out.println("ADConnection.getConnection()");
//		System.err.println(this.getClass().getResource("/us/icitap/timsportal/ws/util/jssecacerts").getFile());
		
		boolean authenticated = false;
		
		Hashtable<String, String> env = new Hashtable<String, String>();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		env.put(Context.REFERRAL, "ignore");
		env.put(Context.SECURITY_PRINCIPAL, username + domain);
		env.put(Context.SECURITY_CREDENTIALS, password);
		
		if(ssl){
			env.put(Context.PROVIDER_URL, "ldaps://"+ipPrimary+":636/"+dc);
			env.put(Context.SECURITY_PROTOCOL, "ssl");
		} else {
			env.put(Context.PROVIDER_URL, "ldap://"+ipPrimary+":389/"+dc);
		}
		LdapContext ctx = null;
		try {
			ctx = new InitialLdapContext(env, null);
			authenticated = true;
		} catch (AuthenticationException e){	
			authenticated = false;
			System.out.println("Authentication fail ! "+ipPrimary);
		} catch (NamingException ne) {
			ne.printStackTrace();
			authenticated = false;
			ctx = null;
			System.err.println(env.get(Context.PROVIDER_URL));
			System.err.println("DirContext fail ! "+ipPrimary);
		}
		if(!authenticated){
			env.remove(Context.PROVIDER_URL);
			if(ssl)
				env.put(Context.PROVIDER_URL, "ldaps://"+ipSecondary+":636/"+dc);
			else env.put(Context.PROVIDER_URL, "ldap://"+ipSecondary+":389/"+dc);
			
			try {
				ctx = new InitialLdapContext(env, null);
				authenticated = true;
			} catch (AuthenticationException e){	
				authenticated = false;
				System.out.println("Authentication fail ! "+ipSecondary);
				ctx = null;
			} catch (NamingException ne) {
				authenticated = false;
				ctx = null;
				System.err.println("DirContext fail ! "+ipSecondary);
			}
		}
		
		return ctx;
	}
	
	public boolean authenticate(String username, String password){
		System.out.println("ADConnection.authenticate()");
		
		LdapContext ctx = getConnection(username, password, false);
		if(ctx==null)
			return false;
		try{
			ctx.close();
		} catch(NamingException ne){
			return false;
		}
		return true;
	}
	
	public List<DomainUser> searchUsersInDomain(String name, String sname){
		List<DomainUser> domainUsers = new ArrayList<DomainUser>();
		
		LdapContext ctx = getConnection("tims.portal", "portal@2013", false);
		if(ctx==null)
			return null;
		
		// Specify the ids of the attributes to return
		String[] attrIDs = {"sAMAccountName", "userPrincipalName", "distinguishedName"};
		SearchControls ctls = new SearchControls();
		ctls.setReturningAttributes(attrIDs);
		ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
		String filter = "(&(givenName=*"+name+"*)(sn=*"+sname+"*))";
		try {
			NamingEnumeration<SearchResult> answer = ctx.search("", filter, ctls);
			while (answer.hasMoreElements()){
				SearchResult sr = (SearchResult) answer.nextElement();				
				Attributes ans = sr.getAttributes();
				Attribute att = ans.get("sAMAccountName");
			    String accountName = (String) att.get();
			    att = ans.get("userPrincipalName");
			    String email = (String) att.get();
			    att = ans.get("distinguishedName");
			    String distinguishedName = (String) att.get();
			    String[] arr = distinguishedName.split(",");
			    arr = arr[1].split("=");
			    String ou = arr[1];
			    			    
			    DomainUser dUser = new DomainUser();
			    dUser.setAccountName(accountName);
			    dUser.setEmail(email);
			    dUser.setOu(ou);
			    domainUsers.add(dUser);
			}
			ctx.close();
		} catch (NamingException e) {
			System.err.println("Search failed !");
			return null;
		}
		
		return domainUsers;
	}
	
	public String getDName(String username, String password){
		String distinguishedName = null;
		LdapContext ctx = getConnection(username, password, false);
		if(ctx==null)
			return null;
//		String[] attrIDs = {"distinguishedName"};
		SearchControls ctls = new SearchControls();
//		ctls.setReturningAttributes(attrIDs);
		ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
		String filter = "(&(sAMAccountName="+username+"))";
		try{
			NamingEnumeration<SearchResult> answer = ctx.search("", filter, ctls);
			while(answer.hasMoreElements()){
				SearchResult sr = (SearchResult) answer.nextElement();
				distinguishedName = sr.getName();
			}
			ctx.close();
		} catch (NamingException e) {
			System.err.println("Search failed !");
			return null;
		}
		return distinguishedName;
	}

	
	public boolean updatePassword(String username, String oldPassword, String newPassword){
		String dName = getDName(username, oldPassword);
		System.err.println(dName);
		
		LdapContext lctx = getConnection(username, oldPassword, true);
		System.err.println(lctx);
		if(lctx==null)
			return false;
		
		try {
			System.out.println("updating password...\n");
			System.out.println();
			ModificationItem[] mods = new ModificationItem[2];
			mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
					new BasicAttribute("unicodePwd", getPassword(oldPassword)));
			mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
					new BasicAttribute("unicodePwd", getPassword(newPassword)));
			lctx.modifyAttributes(dName, mods);
		} catch (AttributeModificationException e) {
			System.out.println("update password error: " + e.getMessage());
			return false;
		} catch (InvalidAttributeValueException e){
			System.out.println("Domain security policy "+e.getMessage());
			return false;
		} catch (NamingException ne){
			System.out.println("ADConnection.updatePassword()"+ne.getMessage());
			return false;
		}
		return true;
	}
	
	private byte[] getPassword(String newPass){
        String quotedPassword = "\"" + newPass + "\"";
        //return quotedPassword.getBytes("UTF-16LE");
        char unicodePwd[] = quotedPassword.toCharArray();
        byte pwdArray[] = new byte[unicodePwd.length * 2];
        for (int i=0; i<unicodePwd.length; i++) {
            pwdArray[i*2 + 1] = (byte) (unicodePwd[i] >>> 8);
            pwdArray[i*2 + 0] = (byte) (unicodePwd[i] & 0xff);
        }
        return pwdArray;
    }

}
